package com.monkeyboy.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author Gavin
 * @date 2021.06.03 16:13
 */
@Configuration
/**
 * 加了这个注解后相当于就添加了一个过滤器{@code OAuth2AuthenticationProcessingFilter}
 * 然后这个过滤器就会用一个{@code OAuth2AuthenticationManager}来校验token
 *
 */
//开启oauth2,auth server模式
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private DataSource dataSource;


    /**
     * -----------------------------客户端信息配置，首先要知道客户端信息是从哪里获取的------------------------------------------------------------
     */
    @Bean
    public ClientDetailsService jdbcClientDetails() {
        // 基于 JDBC 实现，需要事先在数据库配置客户端信息
        //配置客户端存储到db
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }

    /**
     * 配置客户端
     * 就是配置客户端的信息：clientId,secret等信息的获取，这里就把客户端的信息存入到数据库中
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetails());
    }

    /**
     * -----------------------------------------------端点配置------------------------------------------------------------
     */

    @Autowired
    private TokenStore tokenStore;


    /**
     * 配置token管理服务
     * 就是配置token如何存取的逻辑
     * 下面是基于数据库存储token
     */
  /*  @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setClientDetailsService(clientDetailsService);
        defaultTokenServices.setSupportRefreshToken(true);

        //配置token的存储方法
        defaultTokenServices.setTokenStore(tokenStore);
        defaultTokenServices.setAccessTokenValiditySeconds(3000);
        defaultTokenServices.setRefreshTokenValiditySeconds(15000);
        return defaultTokenServices;
    }*/

    /**
     * 这里是基于jwt来存token的，这样资源服务器就可以直接进行校验这个token，
     * 而不需要每次都请求一次授权服务器来校验token以提高性能
     */
    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices service = new DefaultTokenServices();
        service.setClientDetailsService(jdbcClientDetails());
        service.setSupportRefreshToken(true);

        service.setTokenStore(tokenStore);
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);
        return service;
    }

    //密码模式才需要配置,认证管理器
    @Autowired
    private AuthenticationManager authenticationManager;

    //授权码的存储，这里指定存储到数据库里面
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        //设置授权码模式的授权码如何 存取，暂时采用内存方式
        //InMemoryAuthorizationCodeServices();
        //当然也可以继承JdbcAuthorizationCodeServices类，然后重写里面的方法用redis来存取
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     * 自定义异常处理类，该类对/oauth2/token的请求处理异常不起作用，不知道为啥
     */
    @Bean
    WebResponseExceptionTranslator<OAuth2Exception> cusWebResponseExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {
            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {

                ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
                OAuth2Exception body = responseEntity.getBody();
                HttpStatus statusCode = responseEntity.getStatusCode();

                assert body != null;
                body.addAdditionalInformation("timestamp", String.valueOf(System.currentTimeMillis()));
                body.addAdditionalInformation("status", String.valueOf(body.getHttpErrorCode()));
                body.addAdditionalInformation("message", body.getMessage());
                body.addAdditionalInformation("code", body.getOAuth2ErrorCode().toUpperCase());
                HttpHeaders headers = new HttpHeaders();
                headers.setAll(responseEntity.getHeaders().toSingleValueMap());
                // do something with header or response
                return new ResponseEntity<>(body, headers, statusCode);
            }
        };
    }
    @Autowired
    UserDetailsService userDetailsServiceImpl;
    //把上面的各个组件组合在一起
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints.authenticationManager(authenticationManager)//认证管理器
                //配置授权码存储到db
                .authorizationCodeServices(authorizationCodeServices())//授权码管理
                .tokenServices(tokenServices())//token管理
                .userDetailsService(userDetailsServiceImpl)
                .exceptionTranslator(cusWebResponseExceptionTranslator())
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    /**
     * -------------------------端点访问路径的安全约束----------------------------------------------------------------------------------
     */


    //配置端点暴露的/oauth/**接口哪些可以被访问
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("isAuthenticated()")///oauth/token_key允许授权后的用户访问，(允许所有访问，permitAll())
                .checkTokenAccess("permitAll()")///oauth/check_token公开
                .allowFormAuthenticationForClients();//允许表单认证
    }
}
